﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Diagnostics;
using System.Collections;

namespace WaterKit.TcpUdp
{
    /// <summary>
    /// Tcp通讯客户端
    /// </summary>
    public class TcpClient
    {
        #region 字段

        /// <summary>
        /// 客户端与服务器之间的会话类
        /// </summary>
        private Session _session;

        /// <summary>
        /// 客户端是否已经连接服务器
        /// </summary>
        private bool _isConnected = false;

        /// <summary>
        /// 接收数据缓冲区大小64K
        /// </summary>
        public const int DefaultBufferSize = 1024;

        /// <summary>
        /// 接收数据缓冲区
        /// </summary>
        private byte[] _recvDataBuffer = new byte[DefaultBufferSize];

        /// <summary>
        /// 报文结尾标记
        /// </summary>
        private string endTag = "]}";

        #endregion

        #region 事件

        /// <summary>
        /// 已经连接服务器事件
        /// </summary>
        public event NetEvent ConnectedServer;

        /// <summary>
        /// 接收到数据报文事件
        /// </summary>
        public event NetEvent ReceivedDatagram;

        /// <summary>
        /// 连接断开事件
        /// </summary>
        public event NetEvent DisConnectedServer;

        #endregion

        #region 属性

        public bool IsConnected
        {
            get
            {
                return _isConnected;
            }
        }

        #endregion

        #region 共有方法

        /// <summary> 连接服务器</summary>
        /// <param name="ip">服务器IP地址</param>
        /// <param name="port">服务器端口</param>
        public virtual bool Connect(string ip, int port)
        {
            try
            {
                Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                IPEndPoint iep = new IPEndPoint(IPAddress.Parse(ip), port);
                newsock.BeginConnect(iep, new AsyncCallback(Connected), newsock);
                return true;
            }
            catch (System.Net.Sockets.SocketException ex)
            {
                return false;
            }
        }

        /// <summary>
        /// 发送数据报文
        /// </summary>
        /// <param name="datagram"></param>
        public virtual void Send(string datagram)
        {
            if (datagram.Length == 0)
            {
                return;
            }

            if (!_isConnected)
            {
                throw (new ApplicationException("没有连接服务器，不能发送数据"));
            }

            //获得报文的编码字节
            byte[] data = Encoding.UTF8.GetBytes(datagram+endTag);

            _session.ClientSocket.BeginSend(data, 0, data.Length, SocketFlags.None,
             new AsyncCallback(SendDataEnd), _session.ClientSocket);
        }



        /// <summary>
        /// 关闭连接
        /// </summary>
        public virtual void Close()
        {
            if (!_isConnected)
            {
                return;
            }
            _session.Close();
            _session = null;
            _isConnected = false;
        }

        #endregion

        #region 私有方法

        /// <summary>
        /// 数据发送完成处理函数
        /// </summary>
        /// <param name="iar"></param>
        protected virtual void SendDataEnd(IAsyncResult iar)
        {
            Socket remote = (Socket)iar.AsyncState;
            int sent = remote.EndSend(iar);
            Debug.Assert(sent != 0);
        }

        /// <summary>
        /// 连接服务器
        /// </summary>
        /// <param name="iar"></param>
        protected virtual void Connected(IAsyncResult iar)
        {
            try
            {
                Socket socket = (Socket)iar.AsyncState;
                socket.EndConnect(iar);

                //创建新的会话
                _session = new Session(socket);
                _isConnected = true;

                //触发连接建立事件
                if (ConnectedServer != null)
                {
                    ConnectedServer(this, new NetEventArgs(_session));
                }

                _session.ClientSocket.BeginReceive(_recvDataBuffer, 0, DefaultBufferSize, SocketFlags.None,
                 new AsyncCallback(RecvData), socket);
            }
            catch (Exception ex)
            {
                Base.Log4C.WriteErrLog("连接服务器出错", ex);
            }
        }

        /// <summary>
        /// 数据接收处理函数
        /// </summary>
        /// <param name="iar">异步Socket</param>
        protected virtual void RecvData(IAsyncResult iar)
        {
            Socket remote = (Socket)iar.AsyncState;
            try
            {
                int recv = remote.EndReceive(iar);
                if (recv == 0)
                {
                    _session.TypeOfExit = Session.ExitType.NormalExit;

                    if (DisConnectedServer != null)
                    {
                        DisConnectedServer(this, new NetEventArgs(_session));
                    }
                    return;
                }

                string receivedData = Encoding.UTF8.GetString(_recvDataBuffer, 0, recv);

                //通过事件发布收到的报文
                if (ReceivedDatagram != null)
                {
                    if (_session.Datagram != null && _session.Datagram.Length != 0)
                    {
                        receivedData = _session.Datagram + receivedData;
                    }

                    string[] recvDatagrams = Resolve(ref receivedData);
                    foreach (string newDatagram in recvDatagrams)
                    {
                        ICloneable copySession = (ICloneable)_session;
                        Session clientSession = (Session)copySession.Clone();
                        clientSession.Datagram = newDatagram;

                        //发布一个报文消息
                        ReceivedDatagram(this, new NetEventArgs(clientSession));
                    }

                    //剩余的代码片断,下次接收的时候使用
                    _session.Datagram = receivedData;
                }
                else
                {
                    ICloneable copySession = (ICloneable)_session;
                    Session clientSession = (Session)copySession.Clone();
                    clientSession.Datagram = receivedData;

                    ReceivedDatagram(this, new NetEventArgs(clientSession));
                }

                //继续接收数据
                _session.ClientSocket.BeginReceive(_recvDataBuffer, 0, DefaultBufferSize, SocketFlags.None,
                 new AsyncCallback(RecvData), _session.ClientSocket);
            }
            catch (SocketException ex)
            {
                //客户端退出
                if (10054 == ex.ErrorCode)
                {
                    _session.TypeOfExit = Session.ExitType.ExceptionExit;

                    if (DisConnectedServer != null)
                    {
                        DisConnectedServer(this, new NetEventArgs(_session));
                    }
                }
                else
                {
                    throw (ex);
                }
            }
            catch (ObjectDisposedException ex)
            {
                if (ex != null)
                {
                    ex = null;
                }
            }
        }

        /// <summary>
        /// 解析报文,主要是将报文判断结尾标记
        /// </summary>
        /// <param name="rawDatagram"></param>
        /// <returns></returns>
        protected virtual string[] Resolve(ref string rawDatagram)
        {
            ArrayList datagrams = new ArrayList();
            //末尾标记位置索引
            int tagIndex = -1;

            while (true)
            {
                tagIndex = rawDatagram.IndexOf(endTag, tagIndex + 1);
                if (tagIndex == -1)
                {
                    break;
                }
                else
                {
                    //按照末尾标记把字符串分为左右两个部分
                    string newDatagram = rawDatagram.Substring(0, tagIndex + endTag.Length);
                    datagrams.Add(newDatagram);
                    if (tagIndex + endTag.Length >= rawDatagram.Length)
                    {
                        rawDatagram = "";
                        break;
                    }
                    rawDatagram = rawDatagram.Substring(tagIndex + endTag.Length, rawDatagram.Length - newDatagram.Length);
                    //从开始位置开始查找
                    tagIndex = 0;
                }
            }
            string[] results = new string[datagrams.Count];
            datagrams.CopyTo(results);
            return results;
        }

        #endregion
    }
}
